/*
 * Copyright 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.devtools.simple.compiler;

import com.google.devtools.simple.compiler.expressions.ConstantBooleanExpression;
import com.google.devtools.simple.compiler.expressions.ConstantExpression;
import com.google.devtools.simple.compiler.expressions.ConstantNumberExpression;
import com.google.devtools.simple.compiler.expressions.ConstantStringExpression;
import com.google.devtools.simple.compiler.scanner.Scanner;
import com.google.devtools.simple.compiler.scopes.GlobalScope;
import com.google.devtools.simple.compiler.scopes.ObjectScope;
import com.google.devtools.simple.compiler.symbols.AliasSymbol;
import com.google.devtools.simple.compiler.symbols.ConstantDataMemberSymbol;
import com.google.devtools.simple.compiler.symbols.DataMemberSymbol;
import com.google.devtools.simple.compiler.symbols.EventSymbol;
import com.google.devtools.simple.compiler.symbols.FunctionSymbol;
import com.google.devtools.simple.compiler.symbols.InstanceDataMemberSymbol;
import com.google.devtools.simple.compiler.symbols.InstanceFunctionSymbol;
import com.google.devtools.simple.compiler.symbols.ObjectDataMemberSymbol;
import com.google.devtools.simple.compiler.symbols.ObjectFunctionSymbol;
import com.google.devtools.simple.compiler.symbols.ObjectSymbol;
import com.google.devtools.simple.compiler.symbols.PropertySymbol;
import com.google.devtools.simple.compiler.symbols.Symbol;
import com.google.devtools.simple.compiler.types.ArrayType;
import com.google.devtools.simple.compiler.types.BooleanType;
import com.google.devtools.simple.compiler.types.ByteType;
import com.google.devtools.simple.compiler.types.DateType;
import com.google.devtools.simple.compiler.types.DoubleType;
import com.google.devtools.simple.compiler.types.IntegerType;
import com.google.devtools.simple.compiler.types.LongType;
import com.google.devtools.simple.compiler.types.ObjectType;
import com.google.devtools.simple.compiler.types.ShortType;
import com.google.devtools.simple.compiler.types.SingleType;
import com.google.devtools.simple.compiler.types.StringType;
import com.google.devtools.simple.compiler.types.Type;
import com.google.devtools.simple.compiler.types.VariantType;
import com.google.devtools.simple.util.Preconditions;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This class loads and analyzes the Simple runtime library creating the
 * necessary symbols for the compilation process.
 * 
 * <p>Simple classfiles contain annotations marking information visible
 * to the Simple compiler. Classfiles generated by the Simple compiler
 * will add this information automatically. Classfiles generated from
 * Java source code (like the Simple runtime library) need to have the
 * annotations added manually. Otherwise the Simple compiler will not
 * pick up any symbols from the class file.
 *
 * @author Herbert Czymontek
 */
public final class RuntimeLoader {

  /*
   * Special class loader for loading Simple runtime library classes.
   * Needed for injection of a new classpath into a running compiler.
   */
  private static class RuntimeClassLoader extends URLClassLoader {

    public RuntimeClassLoader(URL[] urls, ClassLoader parent) {
      super(urls, parent);
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
      try {
        return findClass(name);
      } catch (Exception e) {
        return super.loadClass(name);
      }
    }
  }

  // Logging support
  private static final Logger LOG = Logger.getLogger(RuntimeLoader.class.getName());

  // Classfile extension
  private static final String CLASSFILE_EXTENSION = ".class";

  // Package where Simple annotations live
  public static final String ANNOTATION_PACKAGE = Compiler.RUNTIME_ROOT_PACKAGE + ".annotations";
  public static final String ANNOTATION_INTERNAL = ANNOTATION_PACKAGE.replace('.', '/');

  // Reference parameter runtime class names
  private static final String REFERENCE_PARAMETER_PACKAGE =
      Compiler.RUNTIME_ROOT_PACKAGE + ".parameters";

  private static final String BOOLEAN_REFERENCE_PARAMETER_NAME =
      REFERENCE_PARAMETER_PACKAGE + ".BooleanReferenceParameter";
  private static final String BYTE_REFERENCE_PARAMETER_NAME =
      REFERENCE_PARAMETER_PACKAGE + ".ByteReferenceParameter";
  private static final String DATE_REFERENCE_PARAMETER_NAME =
      REFERENCE_PARAMETER_PACKAGE + ".DateReferenceParameter";
  private static final String DOUBLE_REFERENCE_PARAMETER_NAME =
      REFERENCE_PARAMETER_PACKAGE + ".DoubleReferenceParameter";
  private static final String INTEGER_REFERENCE_PARAMETER_NAME =
      REFERENCE_PARAMETER_PACKAGE + ".IntegerReferenceParameter";
  private static final String LONG_REFERENCE_PARAMETER_NAME =
      REFERENCE_PARAMETER_PACKAGE + ".LongReferenceParameter";
  private static final String OBJECT_REFERENCE_PARAMETER_NAME =
      REFERENCE_PARAMETER_PACKAGE + ".ObjectReferenceParameter";
  private static final String SHORT_REFERENCE_PARAMETER_NAME =
      REFERENCE_PARAMETER_PACKAGE + ".ShortReferenceParameter";
  private static final String SINGLE_REFERENCE_PARAMETER_NAME =
      REFERENCE_PARAMETER_PACKAGE + ".SingleReferenceParameter";
  private static final String STRING_REFERENCE_PARAMETER_NAME =
      REFERENCE_PARAMETER_PACKAGE + ".StringReferenceParameter";
  private static final String VARIANT_REFERENCE_PARAMETER_NAME =
      REFERENCE_PARAMETER_PACKAGE + ".VariantReferenceParameter";

  // Classloader to load Simple runtime library classes
  private RuntimeClassLoader runtimeClassLoader;

  // Root directory of runtime library
  private final File runtimeRoot;

  // Simple compiler instance
  private final Compiler compiler;

  // Simple class file annotations
  private Class<? extends Annotation> simpleComponent;
  private Class<? extends Annotation> simpleObject;
  private Class<? extends Annotation> simpleDataElement;
  private Class<? extends Annotation> simpleEvent;
  private Class<? extends Annotation> simpleFunction;
  private Class<? extends Annotation> simpleProperty;
  private Class<? extends Annotation> androidUsesPermission;

  private Class<?> simpleReferenceParameter;

  // For mapping components to their implementors
  private List<Class<?>> classList;
  private Map<Class<?>, ObjectSymbol> classMap;
  private Map<ObjectSymbol, String> componentMap;

  /*
   * Loads a Simple class file annotation.
   */
  @SuppressWarnings("unchecked")
  private Class<? extends Annotation> loadAnnotationClass(String name)
      throws ClassNotFoundException {
    return (Class<? extends Annotation>) Class.forName(ANNOTATION_PACKAGE + '.' + name, false,
        runtimeClassLoader);
  }

  /**
   * Get the UsesPermission attribute for this class loader
   *
   * @return  {@code UsesPermissions} class from runtime library
   */
  public Class<? extends Annotation> getAndroidUsesPermission() {
    return androidUsesPermission;
  }

  /**
   * Creates a new Simple runtime loader.
   * 
   * @param compiler  Simple compiler instance
   * @param platformRuntime  path to platform runtime library
   * @param simpleRuntime  path to Simple runtime library
   * 
   * <p>Note: The reason we need the platform runtime library (Android or
   * whatever) is because the Simple runtime library depends on it and
   * therefore not having it would result in ClassNotFoundExceptions when
   * loading Simple classes from its runtime library.
   */
  public RuntimeLoader(Compiler compiler, String platformRuntime, String simpleRuntime) {
    this.compiler = compiler;

    // Instantiate classfile loader for Simple runtime library
    try {
      runtimeClassLoader = new RuntimeClassLoader(new URL[] {
          new File(simpleRuntime).toURI().toURL(), new File(platformRuntime).toURI().toURL() },
          getClass().getClassLoader());
    } catch (MalformedURLException mue) {
      // Cannot happen
    }

    componentMap = new HashMap<ObjectSymbol, String>();
    runtimeRoot = new File(simpleRuntime);

    try {
      // Load Simple classfile annotations
      simpleComponent = loadAnnotationClass("SimpleComponent");
      simpleObject = loadAnnotationClass("SimpleObject");
      simpleDataElement = loadAnnotationClass("SimpleDataElement");
      simpleEvent = loadAnnotationClass("SimpleEvent");
      simpleFunction = loadAnnotationClass("SimpleFunction");
      simpleProperty = loadAnnotationClass("SimpleProperty");
      androidUsesPermission = loadAnnotationClass("UsesPermissions");

      // Load Simple reference parameter superclass
      simpleReferenceParameter = Class.forName(Compiler.RUNTIME_ROOT_PACKAGE +
          ".parameters.ReferenceParameter", false, runtimeClassLoader);

    } catch (ClassNotFoundException e) {
      LOG.log(Level.SEVERE, "Runtime library load failure", e);
      compiler.error(Scanner.NO_POSITION, Error.errReadError, simpleRuntime);
    }
  }

  /*
   * Converts a class to its corresponding Simple type.
   */
  private Type convertToSimpleType(Class<?> cls) {
    if (cls.equals(Boolean.TYPE)) {
      return BooleanType.booleanType;
    } else if (cls.equals(Byte.TYPE)) {
      return ByteType.byteType;
    } else if (cls.equals(Short.TYPE)) {
      return ShortType.shortType;
    } else if (cls.equals(Integer.TYPE)) {
      return IntegerType.integerType;
    } else if (cls.equals(Long.TYPE)) {
      return LongType.longType;
    } else if (cls.equals(Float.TYPE)) {
      return SingleType.singleType;
    } else if (cls.equals(Double.TYPE)) {
      return DoubleType.doubleType;
    } else if (cls.equals(Void.TYPE)) {
      return null;
    } else if (cls.equals(String.class)) {
      return StringType.stringType;
    } else if (cls.equals(GregorianCalendar.class)) {
      return DateType.dateType;
    } else if (cls.getName().equals(VariantType.VARIANT_NAME)) {
      return VariantType.variantType;
    } else if (cls.isArray()) {
      int dimensions = 1;
      cls = cls.getComponentType();
      while (cls.isArray()) {
        dimensions++;
        cls = cls.getComponentType();
      }
      return new ArrayType(convertToSimpleType(cls), dimensions);
    } else if (isSimpleReferenceParameter(cls)) {
      String className = cls.getName(); 
      if (className.equals(BOOLEAN_REFERENCE_PARAMETER_NAME)) {
        return BooleanType.booleanType;
      } else if (className.equals(BYTE_REFERENCE_PARAMETER_NAME)) {
        return ByteType.byteType;
      } else if (className.equals(DATE_REFERENCE_PARAMETER_NAME)) {
        return DateType.dateType;
      } else if (className.equals(DOUBLE_REFERENCE_PARAMETER_NAME)) {
        return DoubleType.doubleType;
      } else if (className.equals(INTEGER_REFERENCE_PARAMETER_NAME)) {
        return IntegerType.integerType;
      } else if (className.equals(LONG_REFERENCE_PARAMETER_NAME)) {
        return LongType.longType;
      } else if (className.equals(SINGLE_REFERENCE_PARAMETER_NAME)) {
        return SingleType.singleType;
      } else if (className.equals(SHORT_REFERENCE_PARAMETER_NAME)) {
        return ShortType.shortType;
      } else if (className.equals(STRING_REFERENCE_PARAMETER_NAME)) {
        return StringType.stringType;
      } else if (className.equals(VARIANT_REFERENCE_PARAMETER_NAME)) {
        return VariantType.variantType;
      } else {
        Preconditions.checkState(className.equals(OBJECT_REFERENCE_PARAMETER_NAME));
        return ObjectType.objectType;
      }
    } else {
      ObjectSymbol objectSymbol =
         ObjectSymbol.getObjectSymbol(compiler, cls.getName().replace('.', '/'));
      return objectSymbol.getType();
    }
  }

  /*
   * Get constant value.
   */
  private ConstantExpression getConstantExpression(Field f) {
    Class<?> cls = f.getType();
    try {
      if (cls.equals(Boolean.TYPE)) {
        return new ConstantBooleanExpression(Scanner.NO_POSITION, f.getBoolean(null));
      } else if (cls.equals(Byte.TYPE)) {
        return new ConstantNumberExpression(Scanner.NO_POSITION, new BigDecimal(f.getByte(null)));
      } else if (cls.equals(Short.TYPE)) {
        return new ConstantNumberExpression(Scanner.NO_POSITION, new BigDecimal(f.getShort(null)));
      } else if (cls.equals(Integer.TYPE)) {
        return new ConstantNumberExpression(Scanner.NO_POSITION, new BigDecimal(f.getInt(null)));
      } else if (cls.equals(Long.TYPE)) {
        return new ConstantNumberExpression(Scanner.NO_POSITION, new BigDecimal(f.getLong(null)));
      } else if (cls.equals(Float.TYPE)) {
        return new ConstantNumberExpression(Scanner.NO_POSITION, new BigDecimal(f.getFloat(null)));
      } else if (cls.equals(Double.TYPE)) {
        return new ConstantNumberExpression(Scanner.NO_POSITION, new BigDecimal(f.getDouble(null)));
      } else if (cls.equals(String.class)) {
        return new ConstantStringExpression(Scanner.NO_POSITION, (String) f.get(null));
      } else {
        return null;
      }
    } catch (IllegalAccessException ignored) {
      // This should never happen
      return null;
    }
  }

  /*
   * Checks whether a class represents a reference parameter.
   */
  private boolean isSimpleReferenceParameter(Class<?> cls) {
    Class<?> supercls = cls.getSuperclass();
    return supercls != null && supercls.equals(simpleReferenceParameter);
  }

  /*
   * Analyzes a classfile for Simple information.
   */
  private void analyzeClassFile(String className) {
    try {
      // Skip classfiles from the annotation package (they don't contain any runtime
      // functionality and are for marking exclusively)
      if (!className.startsWith(ANNOTATION_PACKAGE)) {
        Class<?> cls = Class.forName(className, false, runtimeClassLoader);
        classList.add(cls);

        // Only interested in classes marked as SimpleObjects
        if (cls.isAnnotationPresent(simpleObject)) {
          String internalClassName = cls.getName().replace('.', '/');

          // Add a symbol for the object to the compilers symbol tables
          ObjectSymbol objectSymbol = ObjectSymbol.getObjectSymbol(compiler, internalClassName);

          // Base objects of Simple objects need to be other Simple Objects or java.lang.Object
          Class<?> superclass = cls.getSuperclass();
          if (superclass != null && !superclass.equals(Object.class)) {
            objectSymbol.setBaseObject((ObjectType) ObjectSymbol.getObjectSymbol(compiler, 
                superclass.getCanonicalName().replace('.', '/')).getType());
          }

          // Objects from the package 'com.google.devtools.simple.runtime' and its sub-packages
          // automatically receive an alias entry in the global namespace.
          GlobalScope globalScope = (GlobalScope) compiler.getGlobalNamespaceSymbol().getScope();
          String packageName = cls.getPackage().getName();
          if (packageName.startsWith(Compiler.RUNTIME_ROOT_PACKAGE)) {
            globalScope.enterSymbol(new AliasSymbol(Scanner.NO_POSITION, objectSymbol.getName(),
                objectSymbol));
            LOG.log(Level.FINEST, "Loading runtime: global alias for object " +
                objectSymbol.getName());
          }

          // Check for interface objects
          if (cls.isInterface()) {
            objectSymbol.markAsInterface();
          }

          // Is this a component? Remember it - on a second pass we will attempt to find its
          // implementor
          if (cls.isAnnotationPresent(simpleComponent)) {
            classMap.put(cls, objectSymbol);
          }

          // Next check all methods defined in the class file
          for (Method m : cls.getMethods()) {
            if (m.isAnnotationPresent(simpleProperty)) {
              // This method is either a property getter or property setter
              String propertyName = m.getName();

              // Figure out the property type
              Type type;
              Class<?> returnType = m.getReturnType();
              if (returnType.equals(Void.TYPE)) {
                // This is a property setter and can have at most one parameter (which is the
                // new property value).
                Class<?> params[] = m.getParameterTypes();
                if (params.length != 1) {
                  Compiler.internalError();  // COV_NF_LINE
                }
                type = convertToSimpleType(params[0]);
              } else {
                type = convertToSimpleType(returnType);
              }

              // Add a new symbol for the property to the object's symbol tables. Since the
              // property is split up into getter and setter and we can't track which one we
              // have already seen, we need to check whether there is already a property symbol
              // defined.
              ObjectScope classScope = objectSymbol.getScope();
              Symbol symbol = classScope.lookupInObject(propertyName);
              if (symbol == null) {
                objectSymbol.addProperty(new PropertySymbol(Scanner.NO_POSITION, objectSymbol,
                    propertyName, type), null, null);
              }                
            } else if (m.isAnnotationPresent(simpleFunction)) {
              // This method is a Simple function (or procedure)
              FunctionSymbol function = ((m.getModifiers() & Modifier.STATIC) != 0) ?
                  new ObjectFunctionSymbol(Scanner.NO_POSITION, objectSymbol, m.getName()) :
                  new InstanceFunctionSymbol(Scanner.NO_POSITION, objectSymbol, m.getName());
              function.setResultType(convertToSimpleType(m.getReturnType()));

              // Analyze its result and parameter types
              for (Class<?> argClass : m.getParameterTypes()) {
                function.addParameter(Scanner.NO_POSITION, null, convertToSimpleType(argClass),
                    isSimpleReferenceParameter(argClass));
              }

              // Add symbol for the function to the object's symbol tables
              objectSymbol.addFunction(function);

              // Static functions from objects from the package 'com.google.devtools.simple.runtime'
              // automatically receive an alias entry in the global namespace.
              if (function instanceof ObjectFunctionSymbol &&
                  packageName.equals(Compiler.RUNTIME_ROOT_PACKAGE)) {
                globalScope.enterSymbol(new AliasSymbol(Scanner.NO_POSITION, function.getName(),
                    function));
                LOG.log(Level.FINEST, "Loading runtime: global alias for function " +
                    objectSymbol.getName());
              }

            } else if (m.isAnnotationPresent(simpleEvent)) {
              // This method is a Simple event definition
              EventSymbol event = new EventSymbol(Scanner.NO_POSITION, objectSymbol, m.getName()); 
              // Analyze its result and parameter types
              for (Class<?> argClass : m.getParameterTypes()) {
                event.addParameter(Scanner.NO_POSITION, null, convertToSimpleType(argClass),
                    isSimpleReferenceParameter(argClass));
              }

              // Add symbol for the function to the object's symbol tables
              objectSymbol.addEvent(event);
            }
          }

          // Next check all fields defined in the class file
          for (Field f : cls.getFields()) {
            if (f.isAnnotationPresent(simpleDataElement)) {
              // This field is a Simple data element
              String name = f.getName();
              Type type = convertToSimpleType(f.getType());

              DataMemberSymbol dataMember;
              if ((f.getModifiers() & Modifier.STATIC) != 0) {

                ConstantExpression constExpr = null;
                if ((f.getModifiers() & Modifier.FINAL) != 0) {
                  constExpr = getConstantExpression(f);
                }

                if (constExpr != null) {
                  dataMember = new ConstantDataMemberSymbol(Scanner.NO_POSITION, objectSymbol, name,
                      type, constExpr);
                } else {
                  dataMember = new ObjectDataMemberSymbol(Scanner.NO_POSITION, objectSymbol, name,
                      type);
                }
              } else {
                dataMember = new InstanceDataMemberSymbol(Scanner.NO_POSITION, objectSymbol, name,
                    type);
              }

              // Add symbol for the data member to the object's symbol tables
              objectSymbol.addDataMember(dataMember);

              // Static data members from objects from the package
              // 'com.google.devtools.simple.runtime' automatically receive an alias entry in the
              // global namespace.
              if (dataMember instanceof ObjectDataMemberSymbol &&
                  packageName.equals(Compiler.RUNTIME_ROOT_PACKAGE)) {
                globalScope.enterSymbol(new AliasSymbol(Scanner.NO_POSITION, dataMember.getName(),
                    dataMember));
                LOG.log(Level.FINEST, "Loading runtime: global alias for data member " +
                    objectSymbol.getName());
              }
            }
          }
          // TODO: this is in the wrong place.  We're loading all the runtime library classes here,
          //       not just the ones used by our app. We really only care about the one's used by
          //       the app that we're compiling
          if (cls.isAnnotationPresent(androidUsesPermission)) {
            compiler.addToPermissions(cls, objectSymbol);
          }
        }
      }
    } catch (ClassNotFoundException e) {
      LOG.log(Level.SEVERE, "Runtime library load failure", e);
      compiler.error(Scanner.NO_POSITION, Error.errReadError, className);
    }
  }
  
  /*
   * Recursively visits all directories under the runtime root directory and loads and analyzes
   * any Simple classes found.
   */
  private void visitClassDirectories(File file) {
    if (file.isDirectory()) {
      // Recursively visit directories
      for (String child : file.list()) {
        visitClassDirectories(new File(file, child));
      }
    } else if (file.getName().endsWith(".jar")) {
      // Handle Jar libraries
      try {
        JarFile jar = new JarFile(file, false, JarFile.OPEN_READ);
        Enumeration<JarEntry> jarEntries = jar.entries();
        while (jarEntries.hasMoreElements()) {
          JarEntry entry = jarEntries.nextElement();
          String name = entry.getName();
          if (name.endsWith(CLASSFILE_EXTENSION)) {
            // On Windows platform (and probably always) JarEntry.getName() uses '/' as separator
            analyzeClassFile(name.substring(0,
                name.length() - CLASSFILE_EXTENSION.length()).replace('/', '.'));
          }
        }
      } catch (IOException ioe) {
        LOG.log(Level.SEVERE, "Runtime library load failure", ioe);
        compiler.error(Scanner.NO_POSITION, Error.errReadError, file.getName());
      } catch (SecurityException ignored) {
        // This should never happen. If it does anyway the bucket exception handler will pick it up.
        throw ignored;
      }
    } else {
      String name = file.getName();
      if (name.endsWith(CLASSFILE_EXTENSION)) {
        // Class file
        String path = file.getAbsolutePath();
        analyzeClassFile(path.substring(runtimeRoot.getAbsolutePath().length() + 1,
            path.length() - CLASSFILE_EXTENSION.length()).replace(File.separatorChar, '.'));
      }
    }
  }

  /*
   * Maps components to their actual implementations
   */
  private void findComponentImplementations() {
    for (Class<?> cls : classList) {
      for (Class<?> iface : cls.getInterfaces()) {
        if (checkComponentImplementation(cls, iface)) {
          break;
        }
      }
      checkComponentImplementation(cls, cls.getSuperclass());
    }

    // If there is any component implementations missing, error will be reported
    for (ObjectSymbol component : classMap.values()) {
      compiler.error(Scanner.NO_POSITION, Error.errNoComponentImplementation,
          component.getType().toString());
    }
  }

  private boolean checkComponentImplementation(Class<?> cls, Class<?> implementor) {
    ObjectSymbol component = classMap.get(implementor);
    if (component != null) {
      componentMap.put(component, cls.getName().replace('.', '/'));
      classMap.remove(implementor);
      return true;
    }

    return false;
  }

  /**
   * Returns the internal name of the implementation for the given component
   * object type.
   *
   * @param type  component object type
   * @return  internal name of component object
   */
  String getComponentImplementationInternalName(ObjectType type) {
    return componentMap.get(type.getObjectSymbol());
  }

  /**
   * Loads and analyzes classes in the Simple runtime library and enters there symbol information
   * into the compilers symbol tables.
   */
  public void loadSimpleObjects() {
    // Load all Simple objects
    classList = new ArrayList<Class<?>>();
    classMap = new HashMap<Class<?>, ObjectSymbol>();
    visitClassDirectories(runtimeRoot);
    
    // Find Simple component implementations
    findComponentImplementations();
    classList = null;
    classMap = null;
  }
}
